#!/usr/bin/env python

import sys, time

import asyncore, asynchat, socket, sys ,threading, os , os.path,traceback, time , base64 
import re, urllib, urlparse
import httplib
import Queue
from Foundation import *
import objc
import signal
from owa import *
from settings import *
from macstuff import *
import pickle
objc.setVerbose(1)
pool  = NSAutoreleasePool.alloc().init()

__version__ = 'OWA WRAPPER 0.0.1'


class Interrupted(Exception):
	pass

class Fetcher ( threading.Thread ,MacLog):

	def __init__ ( self, name, owa , queue , cache ):
		threading.Thread.__init__(self)
		self.name = name
		self.owa = owa
		self.queue = queue
		self.cache = []
		
		if cache:
			self.cache = cache
		
		
		
	def getCached(self,id):
		for name , value ,url,perm, headers in self.cache:
			if url==id: return (name,value,url,headers)
		return None
	
	def run ( self ):
		pool  = NSAutoreleasePool.alloc().init()
		try:
			
			self.clog('Fetching :'+self.name)
			cache = self.owa.list('Inbox')
			self.clog('Got list of messages...')
			counter = 0
			self.queue.put(cache)
			self.clog(self.name+" Found "+str(len(cache))+" messages . Stopped")
		except:
			err ="\n".join(traceback.format_exception(*sys.exc_info()))
			self.cerr(self.name + " Error:" +err)
		pool.release()
		
class Scheduler(threading.Thread , MacLog):
	def __init__ ( self, server,delay):
		threading.Thread.__init__(self)
		self.running = True
		self.waiting = True
		self.server = server
		self.delay = delay
		
	def run ( self ):
		pool  = NSAutoreleasePool.alloc().init()
		while self.running:
			try:
				self.server.fetch()
			except:
				pass
			self.waiting = True
			for i in range(0,self.delay):
				if self.running and self.waiting:
					time.sleep(1)
		pool.release()	
	def stop(self):
		self.clog('Atempt to stop scheduler...')
		self.running= False
	def ping(self):
			self.clog('Ping scheduler...')
			self.waiting= False
		
class Channel(asynchat.async_chat,MacLog):
	def __init__(self, conn, server):
		asynchat.async_chat.__init__(self, conn)
		self.size = 0
		self.eraseonexit = False
		self.cache = []
		self.server = server
		self.__line = []
		self.delete = []
		self.push('+OK %s %s' % (socket.getfqdn(), __version__))
		self.set_terminator(TERMINATOR)
		self._activeDataChannel = None
		self.fetcher = None
		self.owa = None
		self.pool  = NSAutoreleasePool.alloc().init()
	def push(self, msg):
		#print msg
		asynchat.async_chat.push(self, msg + TERMINATOR)
		#self.cdebug(repr(msg).replace('%','_'))
	
	def collect_incoming_data(self, data):
		self.__line.append(data)

	def found_terminator(self):
		line = ''.join(self.__line)
		self.__line = []
		if not line:
			self.push('500 Error: bad syntax')
			return
		#self.cdebug(line)
		method = None
		i = line.find(' ')
		if i < 0:
			command = line.upper()
			arg = None
		else:
			command = line[:i].upper()
			arg = line[i+1:].strip()
		method = getattr(self, command, None)
		if not method:
			self.push('-ERR Error : command "%s" not implemented' % command)
			return
		method(arg)
		return

	def refresh(self):
		cache = None
		while not self.queue.empty():
			cache = self.queue.get()
		if cache:
			self.cache = cache
		self.clog(self.username+" got " + str(len(self.cache)) + " in mailbox")
		self.server.setCache(self.username,  self.cache)

			
			
	def LIST(self, arg):
		#self.refresh()
		#print "LIST" , arg
		self.push('+OK')

		for i in range(0, len(self.cache)):
			item = self.cache[i]
			id, size,url,perm, html = item
			self.push('%d %d' % (i+1, int(size)))
		self.push(".")

	def DELE(self, arg):
		if arg:
			item = self.cache[int(arg)-1]
			id, size,url,perm, html = item
			#print "DELETE" , id
			self.delete.append(item)
			self.push('+OK')
		else:
			self.push('-ERR: Syntax: DELE message')
		
	def STAT(self, arg):
		#self.refresh()
		self.size = 0
		for v in self.cache:
			id, size ,url,perm, headers= v
			self.size+=int(size)
		self.push('+OK %d %d' % (len(self.cache), self.size))
		
		
	def USER(self, user):
		self.cdebug('USER call')
		if not user:
			self.cdebug('No username found...')
			self.push('-ERR: Syntax: USER username')
		else:
			self.username = user # Store for later.
			self.login  , at , self.url = self.username.partition('@')
			try:
				self.cdebug('Check if online')
				#site = urllib.urlopen('https://'+self.url+'/exchange/')
				#meta = site.info()
			except:
				self.cdebug('Not Online')
				self.push('-ERR: Server error')
				return
			self.push('+OK Password required')
			self.cdebug('USER call ended.')

			
	def PASS(self, password=''):
			try:
				self.owa = OWA(self.url, self.login, password)
				status, reason = self.owa.login()
				self.password = password
				self.cache = self.server.getCache(self.username)
				self.queue = self.server.register(self.username, self.password)
				self.push('+OK User logged in')
				self.refresh()
			except: 
				err ="\n".join(traceback.format_exception(*sys.exc_info()))
				self.cerr(err)
				self.push('-ERR: Syntax: PASS password')
				self.close()
				
		
	def CAPA(self,args):
		self.push('+OK Capability list follows')
		self.push('TOP')
		self.push('USER')
		self.push('UIDL')
		self.push('.')

	def TOP(self,args):
		params =  args.split(' ')
		if len(params)!=2:
			self.push('-ERR: Syntax: TOP id nrlines')	
		id = params[0]
		index = int(id) - 1
		item = self.cache[index]
		id, size,url, perm,headers = item
		if not headers:
			try:
				status, reason,html,headers = self.owa.getHeader(url)
				self.push('+OK')
				for key , value in headers:
					self.push(key+': '+value+TERMINATOR)
				self.push('.')
			except:
				self.push('-ERR: Error: Header not found')	
	def UIDL(self, arg):
		print arg
		if arg:
			index = int(arg)-1
			if len(self.cache)>index:
				item = self.cache[index]
				id, size ,url,perm, html= item
				id = id.encode('ascii')
				response = '+OK %s %s' % (arg , id)
				self.push(response)
			else:
				self.push('-ERR: Syntax: RETR msg')
		else:
			self.push('+OK')
			for i in range(0, len(self.cache)):
				item = self.cache[i]
				id, size,url,perm, html = item
				id = id.encode('ascii')
				self.push('%d %s' % (i+1, id))
			self.push(".")	
		
	def RETR(self, arg):
		if not arg:
			self.push('-ERR: Syntax: RETR msg')
		else:
			index = int(arg) - 1
			item = self.cache[index]
			id, size,url,perm,html = item
			try:
				print url
				data = self.owa.getMessage(url)
				self.push('+OK')
				for line in data.split(TERMINATOR):
					self.push(line)
				self.push('.')
			except RetrievalError:
				self.push('-ERR: Not available')
			

	def QUIT(self, arg):
		self.push('+OK Goodbye')
		if self.delete:
			self.clog(self.username+": Moving messages to trash...")
		for msg in self.delete:
			id , size, url,perm,html = msg
			self.owa.deleteMessage(url);
			self.cache.remove(msg)
		self.delete = []
		self.server.setDeleted(self.username,self.delete)
		#if self.eraseonexit:
		#	if self.owa:
		#		self.owa.emptyTrash()
		self.close()

class POPServer(asyncore.dispatcher,MacLog):
	def __init__ ( self):
		self.counter = 0
		self.clients = {}
		self.cache = {}
		self.deleted = {}
		self.clog('Server init')
		asyncore.dispatcher.__init__(self)
		pool  = NSAutoreleasePool.alloc().init()
		signal.signal(signal.SIGINT, self.handler)
		signal.signal(signal.SIGHUP, self.handler)
		signal.signal(signal.SIGTERM, self.handler)
		signal.signal(signal.SIGQUIT, self.handler)
		self.port = 8110
		self.delay = 300
		self.loadSettings()
		self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
		self.set_reuse_addr()
		self.bind(('127.0.0.1',self.port))
		self.scheduler = Scheduler(self,self.delay)
		self.scheduler.start()
		self.clog('Server started')
		self.listen(5)

	



	def fetch(self):
		counter = 0
		self.clog('Start fetching')
		for username in self.clients:
			login, password, url, queue = self.clients[username]
			owa  = OWA(url, login, password)
			self.cdebug('Loading Settings')
			self.cdebug('Done')
			
			fetcher = Fetcher(username,owa,queue,self.getCache(username))
			fetcher.start()
			counter+=1
		if counter:
			self.clog('Fetched '+str(counter)+' accounts')
		else:
			self.clog('No fetch done.')
	
			
		
		
	def getCache(self,username):
		if username in self.cache:
			return self.cache[username]
		return []

	def getDeleted(self,username):
		if username in self.deleted:
			return self.deleted[username]
		return []
	
	def setDeleted(self,username, data):
			self.deleted[username] = data
	
		
		
	def loadSettings(self):
		self.config = loadData(SETTINGS_FILE)
		if not self.config:
			self.config = DEFAULT_SETTINGS
			
		LOGLEVEL = self.config['pop3level']
		self.clog('Loglevel: '+repr(LOGLEVEL))

		self.port = self.config['pop3port']
		self.clog('Port: '+repr(self.port))

		self.delay = 60 * self.config['pop3delay']
		self.clog('Port: '+repr(self.delay))

	def setCache(self,username, data):
		self.cache[username] = data
		
	def register(self,username,password):
		if username in self.clients:
			login, password, url, queue = self.clients[username]
			return queue
		else:
			queue =Queue.Queue()
			login  , at , url = username.partition('@')
			self.clients[username] = login, password, url, queue
			self.scheduler.ping()
			return queue
	

	
	def handler(self,signum, frame):
		self.quit()
		self.close()
		
		
	def handle_accept(self):
		conn, addr = self.accept()
		self.clog("Connection from" +  repr(addr))
		channel = Channel(conn, self)
	
		
	def quit(self):
		
		self.scheduler.stop()
			

if __name__=='__main__':
	pop = POPServer()
	try:
		asyncore.loop()
	except KeyboardInterrupt:
		NSLog("pop3Server killed")
	finally:
		NSLog("pop3Server dead")
		pop.quit()	
		
objc.removeAutoreleasePool()